/* Autostop - camera driver
 *
 * Copyright (c) Maciej Gajewski, Grzegorz Latarowski
 * http://code.google.com/p/autostop-automaty-drogowe
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version. NO WARRANTY OF ANY KIND is expressed or implied.
 */

#include <QDataStream>
#include <QTimer>
#include <QImageReader>
#include <QFile>
#include <QFileDialog>
#include <QPainter>

#include "viewerwindow.h"
#include "gexception.h"

// --------------------------- constructor -------------------------
ViewerWindow::ViewerWindow(QWidget *parent)
 : QWidget(parent)
{
	setupUi( this );
	_returnToNormal = false;

	connect( &_socket, SIGNAL(readyRead()), SLOT(socketDataReady()) );
	connect( &_socket, SIGNAL( stateChanged ( QAbstractSocket::SocketState )),
		SLOT(socketStateChanged( QAbstractSocket::SocketState ) ) );
		
	connect( &_udpReceiver, SIGNAL(packetReceived( int , const QByteArray&  )),
		SLOT( udpPacketReceived( int , const QByteArray&  ) ) );
	
	connect( &_udpReceiver, SIGNAL(packetsReceived( const QQueue<QPair<quint16,QByteArray>>& )),
		SLOT( udpPacketsReceived( const QQueue<QPair<quint16,QByteArray>>& ) ) );

	_state = WaitingForHeader;
	_playing = false;
	_request = 0;
	//_udpReceiver.setCongestionEnabled( true ); // set congestion control
	
	// show sample jepg2k on start
	//foreach( QString fmt, QImageReader::supportedImageFormats() ) qDebug("%s", qPrintable( fmt ) );
	labelImage->setPixmap( QPixmap(":/res/sample.jp2") );
}

// --------------------------- destructor -------------------------
ViewerWindow::~ViewerWindow()
{
	// empty
}

// -------------------- on_butonPlay_clicked -----------------------
void ViewerWindow::on_buttonPlay_clicked()
{
	if ( _playing )
	{
		_playing = false;
		buttonPlay->setText( tr("Play") );
	}
	else
	{
		_playing = true;
		buttonPlay->setText( tr("Stop") );
		requestFrame();
	}
}

// -------------------- on_buttonConnect_clicket -----------------------
void ViewerWindow::on_buttonConnect_clicked()
{
	if ( _socket.state() == QAbstractSocket::UnconnectedState )
	{
		_socket.connectToHost( editAddress->text(), 5102 );
	}
}

// ---------------------- socketStateChanged --------------------------
void ViewerWindow::socketStateChanged( QAbstractSocket::SocketState socketState )
{
	switch( socketState )
	{		
		case QAbstractSocket::UnconnectedState:
			Log("Socket: unconnected");
			break;
		case QAbstractSocket::HostLookupState:
			Log("Socket: host lookup");
			break;
		case QAbstractSocket::ConnectingState:
			Log("Socket: connecting");
			break;
		case QAbstractSocket::ConnectedState:
			Log("Socket: connected");
			break;
		case QAbstractSocket::ClosingState:
			Log("Socket: closing");
			break;
		
		default:
		case QAbstractSocket::BoundState:
		case QAbstractSocket::ListeningState:
			Log("Socket: other state");

	}// sw
}

// ---------------------- socketStateChanged --------------------------
void ViewerWindow::socketDataReady()
{
	if ( _state == WaitingForHeader )
	{
		// read header
		readHeader();
	}
	else
	{
		// collect data here
		readData();
	}
}

// ---------------------- Log --------------------------
void ViewerWindow::Log( const QString& text )
{
	textLog->append( text );
}

// ---------------------- readHeader --------------------------
void ViewerWindow::readHeader()
{
	int headerSize = sizeof( quint32 );
	
	if ( _socket.bytesAvailable() >= headerSize )
	{
		QDataStream stream( &_socket );
		
		int frameSize = 0;
		stream >> frameSize;
		// header readed - switxch to data read state
		_state = CollectingData;
		_data.clear();
		_data.reserve( frameSize );
		_bytesToRead = frameSize;
	
		// try read some data
		readData();
		
		// request next frame, if playing
		if ( _playing )
		{
			QTimer::singleShot( ( function() == Movement ) ? 160 : 0 , this, SLOT(requestFrame()) );
		}
	}
}

// ---------------------- readData --------------------------
void ViewerWindow::readData()
{
	quint64 bytesAvailable = _socket.bytesAvailable();
	
	if ( bytesAvailable > 0 )
	{
		quint64 bytesToRead = qMin( bytesAvailable, _bytesToRead );
		_data += _socket.read( bytesToRead );
		_bytesToRead -= bytesToRead;
		
		// all data read - handle and switch to header mode
		if ( _bytesToRead == 0 )
		{
			dataReaded();	// handle frame data
			_state = WaitingForHeader;
			
			// try read header out of remaining bytes
			readHeader();
			// TODO request next frame here
		}
		
	}
}

// ---------------------- dataReaded --------------------------
void ViewerWindow::dataReaded()
{
	// disabled to improve performance
	//Log( QString("Frame get: %1 bytes").arg( _data.size() ) );
	
	// read and decode image
	QDataStream stream( _data );
	
	int width;
	int height;
	int format; // 0 - raw grayscale, 1- jpeg2k
	QByteArray imageData;
	QPixmap pixmap;
	QString data;
	
	Function fun = function();
	
	stream >> width;
	stream >> height;
	stream >> format;
	stream >> imageData;
	
	if ( fun == Barcode || fun == DataMatrix || fun == Ocr )
	{
		stream >> data;
		if ( ! data.isEmpty() )
		{
			Log( QString("DATA:") + data );
		}
	}
	
	//qDebug("width: %d, height: %d", width, height );
	
	// decode raw
	if ( format == 0 )
	{
		QImage image( width, height, QImage::Format_RGB32 );
		
		char* byte = imageData.data();
		for( int y = 0; y < height; y++ )
		{
			for( int x = 0; x< width; x++ )
			{
				if ( byte< imageData.data() + imageData.size()  )
				{
					image.setPixel( x, y, qRgb( *byte, *byte, *byte ) );
					byte++;
				}
				else
				{
					qWarning("image buffer smalles than expected, buf size: %d, image: %d pixels", imageData.size(), width*height );
				}
			}
		}
		
		// scale if small (propably segmented)
		if ( width < 100 )
		{
			pixmap = QPixmap::fromImage( image.scaled( width*8, height*8 ) );
		}
		else
		{
			pixmap = QPixmap::fromImage( image );
		}
	}
	// decode jpeg2k
	else if ( format == 1 )
	{
		pixmap.loadFromData( imageData, "j2c" );
		QFile f( "image.j2c" );
		if ( f.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
		{
			f.write( imageData );
		}
	}
	// unknown
	else
	{
		qWarning("Unknown format: %d", format );
	}
	
	// draw scan frame in datmatrix mode
	/*
	QPainter painter( &pixmap );
	painter.setBrush( Qt::NoBrush );
	painter.setPen( Qt::black );
	painter.drawRect( 245, 215, 150, 150 );
	*/
	
	// display decoded pixmap
	if ( ! pixmap.isNull() )
	{
		labelImage->setPixmap( pixmap );
		labelImage->repaint();
	}
	
	// escape from datmatrix mode
	if ( _returnToNormal )
	{
		rbNormal->setChecked( true );
		_returnToNormal = false;
	}
}


// ---------------------- requestFrame --------------------------
void ViewerWindow::requestFrame()
{
	if ( _socket.state() == QTcpSocket::ConnectedState )
	{
		// the request5 is just 32 bit integer. no interpretation for now
		quint32 request;
		switch( function() )
		{
			case Gray:
				request = 1;
				break;
			case Movement:
				request = 2;
				break;
			case Barcode:
				request = 3;
				break;
			case DataMatrix:
				_returnToNormal = true;
				request = 4;
				break;
			case Ocr:
				request = 5;
				break;
				
			default:
				request = 1; // fallback
		}
		
		QDataStream( & _socket ) << request;
		_socket.waitForBytesWritten( 100 );
		
		//disbaled to improve performace
		//Log( QString("Request sent (%1)").arg( request) );
	}
}

// ---------------------- socketError --------------------------
void ViewerWindow::socketError()
{
	Log( QString("Socket error: %1").arg( _socket.errorString() ) );
}

// ---------------------- button udp --------------------------
void ViewerWindow::on_buttonListenUdp_clicked()
{
	if ( _udpReceiver.isReceiving() )
	{
		_udpReceiver.stopReceiving();
	}
	else
	{
		bool ok = false;
		int port = editUdpPort->text().toInt( & ok );
		if ( ok )
		{
			try
			{
				_udpReceiver.startReceiving( port );
			}
			catch( const GException& e )
			{
				Log( QString("Exception on startReceiving:") + e.getMessage() );
			}
		}
	}
}

// ---------------------- udpPacketReceived --------------------------
/// Processes single UDP packet
void ViewerWindow::udpPacketReceived( int packetId, const QByteArray& data )
{
	_data = data;
	//Log( QString("Packet %1 arrived, size: %2").arg( packetId ). arg( data.size() ) );
	dataReaded();
}

// -------------------- udpPacketsReceived ----------------------------
/// Processes multiple UDP packets, i congestion-control mode
void ViewerWindow::udpPacketsReceived( const QQueue< QPair< quint16, QByteArray> >& packets )
{
	//Log( QString("%1 packets received").arg( packets.size() ) );
	qDebug("%d packets recieved", packets.size() );
	
	// display only last (more recent) frame
	_data = packets.last().second;
	if ( packets.size() > 1 )
	{
		qDebug("data size: %d", _data.size() );
	}
	dataReaded();
}

// -------------------- on_buttonSave_clicked ----------------------------
/// Saves current image to file
void ViewerWindow::on_buttonSave_clicked()
{
	QPixmap pixmap = *labelImage->pixmap();
	QString path = QFileDialog::getSaveFileName( this, tr("Select target image file")
		, QDir::home().absolutePath(), "Images (*.png *.xpm *.jpg)" );
	
	if ( ! path.isEmpty() )
	{
		pixmap.save( path );
	}
}

// -------------------- function ----------------------------
/// Returns currently selected function
;ViewerWindow::Function ViewerWindow::function()
{
	if ( rbNormal->isChecked() ) return Gray;
	if ( rbBarcode->isChecked() ) return Barcode;
	if ( rbDataMatrix->isChecked() ) return DataMatrix;
	if ( rbMovement->isChecked() ) return Movement;
	if ( rbOcr->isChecked() ) return Ocr;
	
	// fallback
	return Gray;
}

// eof 


